def testEncode1(self): # Empty sequence der = DerSequence() self.assertEqual(der.encode(), b('0\x00')) self.assertFalse(der.hasOnlyInts()) # One single-byte integer (zero) der.append(0) self.assertEqual(der.encode(), b('0\x03\x02\x01\x00')) self.assertTrue(der.hasOnlyInts()) # Invariant self.assertEqual(der.encode(), b('0\x03\x02\x01\x00'))
def testEncode2(self): # One single-byte integer (non-zero) der = DerSequence() der.append(127) self.assertEqual(der.encode(), b('0\x03\x02\x01\x7f')) # Indexing der[0] = 1 self.assertEqual(len(der), 1) self.assertEqual(der[0], 1) self.assertEqual(der[-1], 1) self.assertEqual(der.encode(), b('0\x03\x02\x01\x01')) # der[:] = [1] self.assertEqual(len(der), 1) self.assertEqual(der[0], 1) self.assertEqual(der.encode(), b('0\x03\x02\x01\x01'))
def testEncode6(self): # Two integers der = DerSequence() der.append(0x180) der.append(0xFF) self.assertEqual(der.encode(), b('0\x08\x02\x02\x01\x80\x02\x02\x00\xff')) self.assertTrue(der.hasOnlyInts()) # der.append(0x01) der[1:] = [9, 8] self.assertEqual(len(der), 3) self.assertEqual(der[1:], [9, 8]) self.assertEqual(der[1:-1], [9]) self.assertEqual(der.encode(), b('0\x0A\x02\x02\x01\x80\x02\x01\x09\x02\x01\x08'))
def testEncode4(self): # One very long integer der = DerSequence() der.append(2**2048) self.assertEqual( der.encode(), b('0\x82\x01\x05') + b('\x02\x82\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00'))
def EMSA_PKCS1_V1_5_ENCODE(hash, emLen): """ Implement the ``EMSA-PKCS1-V1_5-ENCODE`` function, as defined in PKCS#1 v2.1 (RFC3447, 9.2). ``EMSA-PKCS1-V1_5-ENCODE`` actually accepts the message ``M`` as input, and hash it internally. Here, we expect that the message has already been hashed instead. :Parameters: hash : hash object The hash object that holds the digest of the message being signed. emLen : int The length the final encoding must have, in bytes. :attention: the early standard (RFC2313) stated that ``DigestInfo`` had to be BER-encoded. This means that old signatures might have length tags in indefinite form, which is not supported in DER. Such encoding cannot be reproduced by this function. :attention: the same standard defined ``DigestAlgorithm`` to be of ``AlgorithmIdentifier`` type, where the PARAMETERS item is optional. Encodings for ``MD2/4/5`` without ``PARAMETERS`` cannot be reproduced by this function. :Return: An ``emLen`` byte long string that encodes the hash. """ # First, build the ASN.1 DER object DigestInfo: # # DigestInfo ::= SEQUENCE { # digestAlgorithm AlgorithmIdentifier, # digest OCTET STRING # } # # where digestAlgorithm identifies the hash function and shall be an # algorithm ID with an OID in the set PKCS1-v1-5DigestAlgorithms. # # PKCS1-v1-5DigestAlgorithms ALGORITHM-IDENTIFIER ::= { # { OID id-md2 PARAMETERS NULL }| # { OID id-md5 PARAMETERS NULL }| # { OID id-sha1 PARAMETERS NULL }| # { OID id-sha256 PARAMETERS NULL }| # { OID id-sha384 PARAMETERS NULL }| # { OID id-sha512 PARAMETERS NULL } # } # digestAlgo = DerSequence([hash.oid, DerNull().encode()]) digest = DerOctetString(hash.digest()) digestInfo = DerSequence([digestAlgo.encode(), digest.encode()]).encode() # We need at least 11 bytes for the remaining data: 3 fixed bytes and # at least 8 bytes of padding). if emLen < len(digestInfo) + 11: raise ValueError( "Selected hash algorith has a too long digest (%d bytes)." % len(digest)) PS = bchr(0xFF) * (emLen - len(digestInfo) - 3) return b("\x00\x01") + PS + bchr(0x00) + digestInfo
def testEncode6(self): # One integer and another type (no matter what it is) der = DerSequence() der.append(0x180) der.append(b('\x00\x02\x00\x00')) self.assertEqual(der.encode(), b('0\x08\x02\x02\x01\x80\x00\x02\x00\x00')) self.assertFalse(der.hasOnlyInts())
def testEncode5(self): # One single-byte integer (looks negative) der = DerSequence() der.append(0xFF) self.assertEqual(der.encode(), b('0\x04\x02\x02\x00\xff'))
def testEncode3(self): # One multi-byte integer (non-zero) der = DerSequence() der.append(0x180) self.assertEqual(der.encode(), b('0\x04\x02\x02\x01\x80'))
def exportKey(self, format='PEM', passphrase=None, pkcs=1): """Export this RSA key. :Parameter format: The format to use for wrapping the key. - *'DER'*. Binary encoding, always unencrypted. - *'PEM'*. Textual encoding, done according to `RFC1421`_/`RFC1423`_. Unencrypted (default) or encrypted. - *'OpenSSH'*. Textual encoding, done according to OpenSSH specification. Only suitable for public keys (not private keys). :Type format: string :Parameter passphrase: In case of PEM, the pass phrase to derive the encryption key from. :Type passphrase: string :Parameter pkcs: The PKCS standard to follow for assembling the key. You have two choices: - with **1**, the public key is embedded into an X.509 `SubjectPublicKeyInfo` DER SEQUENCE. The private key is embedded into a `PKCS#1`_ `RSAPrivateKey` DER SEQUENCE. This mode is the default. - with **8**, the private key is embedded into a `PKCS#8`_ `PrivateKeyInfo` DER SEQUENCE. This mode is not available for public keys. PKCS standards are not relevant for the *OpenSSH* format. :Type pkcs: integer :Return: A byte string with the encoded public or private half. :Raise ValueError: When the format is unknown. .. _RFC1421: http://www.ietf.org/rfc/rfc1421.txt .. _RFC1423: http://www.ietf.org/rfc/rfc1423.txt .. _`PKCS#1`: http://www.ietf.org/rfc/rfc3447.txt .. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt """ if passphrase is not None: passphrase = tobytes(passphrase) if format == 'OpenSSH': eb = long_to_bytes(self.e) nb = long_to_bytes(self.n) if bord(eb[0]) & 0x80: eb = bchr(0x00) + eb if bord(nb[0]) & 0x80: nb = bchr(0x00) + nb keyparts = ['ssh-rsa', eb, nb] keystring = ''.join( [struct.pack(">I", len(kp)) + kp for kp in keyparts]) return 'ssh-rsa ' + binascii.b2a_base64(keystring)[:-1] # DER format is always used, even in case of PEM, which simply # encodes it into BASE64. der = DerSequence() if self.has_private(): keyType = {1: 'RSA PRIVATE', 8: 'PRIVATE'}[pkcs] der[:] = [ 0, self.n, self.e, self.d, self.p, self.q, self.d % (self.p - 1), self.d % (self.q - 1), inverse(self.q, self.p) ] if pkcs == 8: derkey = der.encode() der = DerSequence([0]) der.append(algorithmIdentifier) der.append(DerObject('OCTET STRING', derkey).encode()) else: keyType = "PUBLIC" der.append(algorithmIdentifier) bitmap = DerObject('BIT STRING') derPK = DerSequence([self.n, self.e]) bitmap.payload = bchr(0x00) + derPK.encode() der.append(bitmap.encode()) if format == 'DER': return der.encode() if format == 'PEM': pem = b("-----BEGIN " + keyType + " KEY-----\n") objenc = None if passphrase and keyType.endswith('PRIVATE'): # We only support 3DES for encryption import Crypro.Hash.MD5 from Crypro.Cipher import DES3 from Crypro.Protocol.KDF import PBKDF1 salt = self._randfunc(8) key = PBKDF1(passphrase, salt, 16, 1, Crypro.Hash.MD5) key += PBKDF1(key + passphrase, salt, 8, 1, Crypro.Hash.MD5) objenc = DES3.new(key, Crypro.Cipher.DES3.MODE_CBC, salt) pem += b('Proc-Type: 4,ENCRYPTED\n') pem += b('DEK-Info: DES-EDE3-CBC,') + binascii.b2a_hex( salt).upper() + b('\n\n') binaryKey = der.encode() if objenc: # Add PKCS#7-like padding padding = objenc.block_size - len( binaryKey) % objenc.block_size binaryKey = objenc.encrypt(binaryKey + bchr(padding) * padding) # Each BASE64 line can take up to 64 characters (=48 bytes of data) chunks = [ binascii.b2a_base64(binaryKey[i:i + 48]) for i in range(0, len(binaryKey), 48) ] pem += b('').join(chunks) pem += b("-----END " + keyType + " KEY-----") return pem return ValueError( "Unknown key format '%s'. Cannot export the RSA key." % format)