/**
 * Package gcm implements GCM mode for block ciphers.
 */ import { AEAD } from "@stablelib/aead" ; import { BlockCipher } from "@stablelib/blockcipher" ; import { CTR } from "@stablelib/ctr" ; import { wipe } from "@stablelib/wipe" ; import { writeUint32BE } from "@stablelib/binary" ; import { equal } from "@stablelib/constant-time" ; export const NONCE_LENGTH = 12 ; export const TAG_LENGTH = 16 ; /**
 * Galois/Counter Mode AEAD
 *
 * Defined in NIST SP-800-38D.
 *
 * This implementation only supports 12-byte nonces and 16-byte tags:
 * these parameters are the most secure and most commonly used.
 */ export class GCM implements AEAD { readonly nonceLength = NONCE_LENGTH ; readonly tagLength = TAG_LENGTH ; private _subkey : Uint8Array ; private _cipher : BlockCipher ; /**
     * Creates a new GCM instance with the given block cipher.
     *
     * Block size of cipher must be equal to 16.
     */ constructor ( cipher ) { if ( cipher . blockSize !== 16 ) { throw new Error ( "GCM supports only 16-byte block cipher" ) ; } this . _cipher = cipher ; this . _subkey = new Uint8Array ( this . _cipher . blockSize ) ; this . _cipher . encryptBlock ( new Uint8Array ( this . _cipher . blockSize ) , this . _subkey ) ; } /**
     * Encrypts and authenticates plaintext, authenticates associated data,
     * and returns sealed ciphertext, which includes authentication tag.
     *
     * If dst is given (it must be the size of plaintext + the size of tag length)
     * the result will be put into it. Dst and plaintext must not overlap.
     */ seal ( nonce , plaintext , associatedData ? , dst ? ) { if ( nonce . length !== this . nonceLength ) { throw new Error ( "GCM: incorrect nonce length" ) ; } const blockSize = this . _cipher . blockSize ; const resultLength = plaintext . length + this . tagLength ; let result ; if ( dst ) { if ( dst . length !== resultLength ) { throw new Error ( "GCM: incorrect destination length" ) ; } result = dst ; } else { result = new Uint8Array ( resultLength ) ; } const counter = new Uint8Array ( blockSize ) ; counter . set ( nonce ) ; counter [ counter . length - 1 ] = 1 ; const tagMask = new Uint8Array ( blockSize ) ; this . _cipher . encryptBlock ( counter , tagMask ) ; counter [ counter . length - 1 ] = 2 ; const ctr = new CTR ( this . _cipher , counter ) ; ctr . streamXOR ( plaintext , result ) ; ctr . clean ( ) ; this . _authenticate ( result . subarray ( result . length - this . tagLength , result . length ) , tagMask , result . subarray ( 0 , result . length - this . tagLength ) , associatedData ) ; wipe ( counter ) ; wipe ( tagMask ) ; return result ; } /**
     * Authenticates sealed ciphertext (which includes authentication tag) and
     * associated data, decrypts ciphertext and returns decrypted plaintext.
     *
     * If authentication fails, it returns null.
     *
     * If dst is given (it must be of ciphertext length minus tag length),
     * the result will be put into it. Dst and plaintext must not overlap.
     */ open ( nonce , sealed , associatedData ? , dst ? ) { if ( nonce . length !== this . nonceLength ) { throw new Error ( "GCM: incorrect nonce length" ) ; } if ( sealed . length < this . tagLength ) { return null ; } const blockSize = this . _cipher . blockSize ; const counter = new Uint8Array ( blockSize ) ; counter . set ( nonce ) ; counter [ counter . length - 1 ] = 1 ; const tagMask = new Uint8Array ( blockSize ) ; this . _cipher . encryptBlock ( counter , tagMask ) ; counter [ counter . length - 1 ] = 2 ; const calculatedTag = new Uint8Array ( this . tagLength ) ; this . _authenticate ( calculatedTag , tagMask , sealed . subarray ( 0 , sealed . length - this . tagLength ) , associatedData ) ; if ( ! equal ( calculatedTag , sealed . subarray ( sealed . length - this . tagLength , sealed . length ) ) ) { return null ; } const resultLength = sealed . length - this . tagLength ; let result ; if ( dst ) { if ( dst . length !== resultLength ) { throw new Error ( "GCM: incorrect destination length" ) ; } result = dst ; } else { result = new Uint8Array ( resultLength ) ; } const ctr = new CTR ( this . _cipher , counter ) ; ctr . streamXOR ( sealed . subarray ( 0 , sealed . length - this . tagLength ) , result ) ; ctr . clean ( ) ; wipe ( counter ) ; wipe ( tagMask ) ; return result ; } clean ( ) { wipe ( this . _subkey ) ; return this ; } private _authenticate ( tagOut , tagMask , ciphertext , associatedData ? ) { const blockSize = this . _cipher . blockSize ; if ( associatedData ) { for ( let i = 0 ; i < associatedData . length ; i += blockSize ) { const slice = associatedData . subarray ( i , Math . min ( i + blockSize , associatedData . length ) ) ; addmul ( tagOut , slice , this . _subkey ) ; } } for ( let i = 0 ; i < ciphertext . length ; i += blockSize ) { const slice = ciphertext . subarray ( i , Math . min ( i + blockSize , ciphertext . length ) ) ; addmul ( tagOut , slice , this . _subkey ) ; } const lengthsBlock = new Uint8Array ( blockSize ) ; if ( associatedData ) { writeBitLength ( associatedData . length , lengthsBlock , 0 ) ; } writeBitLength ( ciphertext . length , lengthsBlock , 8 ) ; addmul ( tagOut , lengthsBlock , this . _subkey ) ; for ( let i = 0 ; i < tagMask . length ; i ++ ) { tagOut [ i ] ^= tagMask [ i ] ; } wipe ( lengthsBlock ) ; } } function writeBitLength ( byteLength , dst , offset = 0 ) { const hi = ( byteLength / 0x20000000 ) | 0 ; const lo = byteLength << 3 ; writeUint32BE ( hi , dst , offset + 0 ) ; writeUint32BE ( lo , dst , offset + 4 ) ; } /**
 * Add and multiply in GF(2^128)
 *
 * a = (a + x) * y in the finite field
 *
 * a is 16 bytes
 * y is 16 bytes
 * x is 0-16 bytes, if x.length <= 16; x is implicitly 0-padded
 *
 * Masking idea from Mike Belopuhov's implementation,
 * that credits John-Mark Gurney for the idea.
 * http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/crypto/gmac.c
 *
 * Addition with implicit padding before multiplication
 * is due to Daniel J. Bernstein's implementation in SUPERCOP.
 */ function addmul ( a , x , y ) { for ( let i = 0 ; i < x . length ; i ++ ) { a [ i ] ^= x [ i ] ; } let v0 = ( y [ 3 ] | y [ 2 ] << 8 | y [ 1 ] << 16 | y [ 0 ] << 24 ) ; let v1 = ( y [ 7 ] | y [ 6 ] << 8 | y [ 5 ] << 16 | y [ 4 ] << 24 ) ; let v2 = ( y [ 11 ] | y [ 10 ] << 8 | y [ 9 ] << 16 | y [ 8 ] << 24 ) ; let v3 = ( y [ 15 ] | y [ 14 ] << 8 | y [ 13 ] << 16 | y [ 12 ] << 24 ) ; let z0 = 0 , z1 = 0 , z2 = 0 , z3 = 0 ; for ( let i = 0 ; i < 128 ; i ++ ) { let mask = ~ ( ( ( ( - ( a [ i >> 3 ] & ( 1 << ( ~ i & 7 ) ) ) ) >>> 31 ) & 1 ) - 1 ) ; z0 ^= v0 & mask ; z1 ^= v1 & mask ; z2 ^= v2 & mask ; z3 ^= v3 & mask ; mask = ~ ( ( v3 & 1 ) - 1 ) ; v3 = ( v2 << 31 ) | ( v3 >>> 1 ) ; v2 = ( v1 << 31 ) | ( v2 >>> 1 ) ; v1 = ( v0 << 31 ) | ( v1 >>> 1 ) ; v0 = ( v0 >>> 1 ) ^ ( 0xe1000000 & mask ) ; } writeUint32BE ( z0 , a , 0 ) ; writeUint32BE ( z1 , a , 4 ) ; writeUint32BE ( z2 , a , 8 ) ; writeUint32BE ( z3 , a , 12 ) ; }